//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

//#include <buola/image/algorithm/transform.h>
#include <buola/image/format.h>
#include <buola/utility/cfixed.h>
#include <array>

namespace buola { namespace img {

//TODO:implement 5x5

static const int sInitValue=INT_MAX>>2;

void init_temp(const CMView_gray32s &pView,int pBorder)
{
    for(int i=0;i<pBorder;i++)
    {
        auto lTop=pView.Row(i).begin();
        auto lBottom=pView.Row(pView.Height()-1-i).begin();
        
        for(int x=0;x<pView.Width();x++)
        {
            lTop[x]=sInitValue;
            lBottom[x]=sInitValue;
        }
    }
}

static void dist_transform_impl(const CView_gray8 &pSrc,const CMView_gray32s &pTemp,const CMView_gray32f &pDst,
                         const CMView_xy32s &pOri,const std::array<float,3> &pMask)
{
    typedef CFixed<int,16> tFixed;

    const int cBorder=1;
    const tFixed cHV(pMask[0]),cDiag(pMask[1]);
    
    const float cScale=1.0f/(1<<16);

    init_temp(pTemp,cBorder);

    // forward pass
    for(int i=0;i<pSrc.Height();i++)
    {
        //TODO: create iterators/row views that allow access to a row and the previous/next. maybe integrate it
        //with crowbuffer.
        auto lOriIt=pOri.Row(i).begin();
        auto lOriUpIt=pOri.Row(i-1).begin();
        auto lSrcIt=pSrc.Row(i).begin();
        auto lTmpIt=pTemp.Row(i+cBorder).begin()+cBorder;
        auto lTmpUpIt=pTemp.Row(i-1+cBorder).begin()+cBorder;

        for(int j=0;j<cBorder;j++)
        {
            lTmpIt[-j-1]=sInitValue;
            lTmpIt[pSrc.Width()+j]=sInitValue;
        }

        for(int j=0;j<pSrc.Width();j++)
        {
            if(!lSrcIt[j])
            {
                lTmpIt[j]=0;
                lOriIt[j]={j,i};
            }
            else
            {
                int lMinIndex=-1;
                int lMin=sInitValue;
                int lT=lTmpIt[j-1]+cHV.Get();
                if(lT<lMin) {   lMin=lT; lMinIndex=0; }
                lT=lTmpUpIt[j-1]+cDiag.Get();
                if(lT<lMin) {   lMin=lT; lMinIndex=1; }
                lT=lTmpUpIt[j]+cHV.Get();
                if(lT<lMin) {   lMin=lT; lMinIndex=2; }
                lT=lTmpUpIt[j+1]+cDiag.Get();
                if(lT<lMin) {   lMin=lT; lMinIndex=3; }

                lTmpIt[j]=lMin;
                switch(lMinIndex)
                {
                case -1:
                    lOriIt[j]={-1,-1};
                    break;
                case 0:
                    lOriIt[j]=lOriIt[j-1];
                    break;
                case 1:
                    lOriIt[j]=lOriUpIt[j-1];
                    break;
                case 2:
                    lOriIt[j]=lOriUpIt[j];
                    break;
                case 3:
                    lOriIt[j]=lOriUpIt[j+1];
                    break;
                default:
                    break;
                }
            }
        }
    }

    // backward pass
    for(int i=pSrc.Height()-1;i>=0;i--)
    {
        //TODO: create iterators/row views that allow access to a row and the previous/next. maybe integrate it
        //with crowbuffer.
        auto lDstIt=pDst.Row(i).begin();
        auto lOriIt=pOri.Row(i).begin();
        auto lOriDownIt=pOri.Row(i+1).begin();
        auto lTmpIt=pTemp.Row(i+cBorder).begin()+cBorder;
        auto lTmpDownIt=pTemp.Row(i+1+cBorder).begin()+cBorder;

        for(int j=pSrc.Width()-1;j>=0;j--)
        {
            int lMinIndex=-1;
            int lMin=lTmpIt[j];
            int lT=lTmpIt[j+1]+cHV.Get();
            if(lT<lMin) {   lMin=lT; lMinIndex=0; }
            lT=lTmpDownIt[j-1]+cDiag.Get();
            if(lT<lMin) {   lMin=lT; lMinIndex=1; }
            lT=lTmpDownIt[j]+cHV.Get();
            if(lT<lMin) {   lMin=lT; lMinIndex=2; }
            lT=lTmpDownIt[j+1]+cDiag.Get();
            if(lT<lMin) {   lMin=lT; lMinIndex=3; }

            if(lMinIndex!=-1)
            {
                lTmpIt[j]=lMin;
                switch(lMinIndex)
                {
                case 0:
                    lOriIt[j]=lOriIt[j+1];
                    break;
                case 1:
                    lOriIt[j]=lOriDownIt[j-1];
                    break;
                case 2:
                    lOriIt[j]=lOriDownIt[j];
                    break;
                case 3:
                    lOriIt[j]=lOriDownIt[j+1];
                    break;
                default:
                    break;
                }
            }
            
            lDstIt[j]=(float)lMin*cScale;
        }
    }
}

static void get_dist_mask(EDistType pDist,int pSize,std::array<float,3> &pMask)
{
    switch(pDist)
    {
    default:
    case DIST_C:
        pMask[0]=1.0f;
        pMask[1]=1.0f;
        pMask[2]=2.0f;
        break;
    
    case DIST_L1:
        pMask[0]=1.0f;
        pMask[1]=2.0f;
        pMask[2]=3.0f;
        break;
    
    case DIST_L2:
        if(pSize==3)
        {
            pMask[0]=0.955f;
            pMask[1]=1.3693f;
        }
        else
        {
            pMask[0]=1.0f;
            pMask[1]=1.4f;
            pMask[2]=2.1969f;
        }
        break;
    }
}

    
void dist_transform(const CView_gray8 &pSrc,const CMView_gray32f &pDst,EDistType pDistType,int pMaskSize,
                    const CMView_xy32s &pOri)
{
    if(pMaskSize!=3&&pMaskSize!=5)
        throw XArgument("wrong mask size in dist_transform");
    
    std::array<float,3> lMask;
    
    get_dist_mask(pDistType,pMaskSize,lMask);

    int lBorder=pMaskSize==3?1:2;
    
    CImage_gray32s lTemp(pSrc.Size()+2*lBorder);
    
    dist_transform_impl(pSrc,lTemp,pDst,pOri,lMask);
}

/*namespace image*/ } /*namespace buola*/ }
